package com.czn.shopdemo.utils;

import cn.hutool.core.util.ZipUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.czn.shopdemo.config.MinioConfig;
import com.czn.shopdemo.model.vo.FileVO;
import io.minio.MinioClient;
import io.minio.PutObjectOptions;
import io.minio.Result;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;

/**
 * @author Zenan Chen
 * @date 2023/4/3 14:01
 */
@Component
public class MinioUtil {

    private static final Logger log = LoggerFactory.getLogger(MinioUtil.class);
    @Autowired
    MinioConfig minioConfig;

    private static MinioClient minioClient;

    /**
     * 初始化minio配置
     *
     * @param :
     * @return: void
     * @date :
     */

    @PostConstruct
    public void init() {
        try {
            minioClient = new MinioClient(minioConfig.getEndpoint(), minioConfig.getPort(), minioConfig.getAccessKey(), minioConfig.getSecretKey(), false);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("minioncliet init error: ", e.fillInStackTrace());
        }
    }

    /**
     * 判断 bucket是否存在
     *
     * @param bucketName: 文件桶名
     * @return: boolean
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(bucketName);
    }

    /**
     * 创建 bucket
     *
     * @param bucketName: 文件桶名
     * @return: void
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static void createBucket(String bucketName) {
        boolean isExist = minioClient.bucketExists(bucketName);
        if (!isExist) {
            minioClient.makeBucket(bucketName);
        }
    }

    /**
     * 获取全部bucket
     *
     * @param :
     * @return: java.util.List<io.minio.messages.Bucket>
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static List<Bucket> getAllBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 文件上传
     *
     * @param bucketName: 文件桶名
     * @param fileName:   文件名
     * @param filePath:   文件路径
     * @return: void
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static void upload(String bucketName, String fileName, String filePath) {
        minioClient.putObject(bucketName, fileName, filePath, null);
    }

    /**
     * 文件上传
     *
     * @param bucketName: 文件桶名
     * @param fileName:   文件名
     * @param stream:     文件流
     * @return: java.lang.String : 文件url地址
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static String upload(String bucketName, String fileName, InputStream stream) {
        minioClient.putObject(bucketName, fileName, stream, new PutObjectOptions(stream.available(), -1));
        return getFileUrl(bucketName, fileName);
    }

    /**
     * 文件上传
     *
     * @param bucketName: 文件桶名
     * @param file:       文件
     * @return: java.lang.String : 文件url地址
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static String upload(String bucketName, MultipartFile file) {
        final InputStream is = file.getInputStream();
        final String fileName = file.getOriginalFilename();
        PutObjectOptions objectOptions = new PutObjectOptions(is.available(), -1);
        objectOptions.setContentType(file.getContentType());
        minioClient.putObject(bucketName, fileName, is, objectOptions);
        is.close();
        return getFileUrl(bucketName, fileName);
    }

    /**
     * 删除文件
     *
     * @param bucketName: 文件桶名
     * @param fileName:   文件名
     * @return: void
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static void deleteFile(String bucketName, String fileName) {
        minioClient.removeObject(bucketName, fileName);
    }

    /**
     * 删除文件夹
     *
     * @param bucketName 桶名称
     */
    @SneakyThrows(Exception.class)
    public static void deleteFolder(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(bucketName);
        if (!results.iterator().hasNext()) {
            minioClient.removeBucket(bucketName);
        } else {
            throw new RuntimeException("桶中已存在数据，请先删除数据！");
        }
    }

    /**
     * 获取minio文件的下载地址
     *
     * @param bucketName: 文件桶名
     * @param fileName:   文件名
     * @return: java.lang.String
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static String getFileUrl(String bucketName, String fileName) {
//        return minioClient.presignedGetObject(bucketName, fileName);
        return minioClient.getObjectUrl(bucketName, fileName);
    }

    /**
     * 下载文件
     *
     * @param bucketName: 文件桶名
     * @param fileName:   文件名
     * @param
     * @return: void
     * @date :
     */
    @SneakyThrows(Exception.class)
    public static void download(String bucketName, String fileName, HttpServletResponse response) {
        if (StringUtils.isBlank(fileName)) {
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            String data = "文件下载失败";
            OutputStream ps = response.getOutputStream();
            ps.write(data.getBytes("UTF-8"));
        }
        // 获取文件对象
        log.info("--------------------------> fileName: " + fileName);
        InputStream download = minioClient.getObject(bucketName, fileName);
        byte buf[] = new byte[1024];
        int length = 0;
        response.reset();
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding("UTF-8");
        OutputStream outputStream = response.getOutputStream();
        // 输出文件
        while ((length = download.read(buf)) > 0) {
            outputStream.write(buf, 0, length);
        }
        // 关闭输出流
        outputStream.close();
    }

    /**
     * 获取桶下的所有文件
     *
     * @param bucketName
     * @return
     */
    @SneakyThrows(Exception.class)
    public static List<FileVO> getFileList(String bucketName, Integer pageNum, Integer pageSize) {
        List<FileVO> list = new ArrayList<>();
        DecimalFormat df = new DecimalFormat("0.00");
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(bucketName);
            results.forEach(b -> {
                try {
                    FileVO FileVO = new FileVO();
                    FileVO.setBucketName(bucketName);  // 文件夹名称
                    FileVO.setFileName(b.get().objectName());  // 文件名称
                    FileVO.setUpdateTime(localDateTime2Date(b.get().lastModified().toLocalDateTime()));  // 文件上传时间
                    Long size = b.get().size();
                    if (size > (1024 * 1024)) {
                        FileVO.setFileSize(df.format(((double) size / 1024 / 1024)) + "MB");  // 文件大小，如果超过1M，则把单位换成MB
                    } else if (size > 1024) {
                        FileVO.setFileSize(df.format(((double) size / 1024)) + "KB"); // 文件大小，如果没超过1M但是超过1000字节，则把单位换成KB
                    } else {
                        FileVO.setFileSize(size + "bytes");  // // 文件大小，如果没超过1000字节，则把单位换成bytes
                    }
                    list.add(FileVO);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        // 按上传时间倒叙
        list.sort(new Comparator<FileVO>() {
            @Override
            public int compare(FileVO o1, FileVO o2) {
                return o2.getUpdateTime().compareTo(o1.getUpdateTime());
            }
        });
        List returnList = PageUtil.startPage(list, pageNum, pageSize);
        return returnList;
    }

    /**
     * 压缩桶下的所有文件
     *
     * @param bucketName
     * @return
     */
    @SneakyThrows(Exception.class)
    public static void getFileZip(String bucketName, HttpServletResponse response) {
        Iterable<Result<Item>> results = minioClient.listObjects(bucketName);
        List<String> fileNames = new ArrayList<>();
        results.forEach(a -> {
            try {
                String fileName = a.get().objectName();
                fileNames.add(fileName);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        //被压缩文件InputStream
        InputStream[] srcFiles = new InputStream[fileNames.size()];
        //被压缩文件名称
        String[] srcFileNames = new String[fileNames.size()];
        for (int i = 0; i < fileNames.size(); i++) {
            String fileUrl = fileNames.get(i);
            InputStream inputStream = minioClient.getObject(bucketName, fileUrl);
            if (inputStream == null) {
                continue;
            }
            srcFiles[i] = inputStream;
            String[] splitFileUrl = fileUrl.split("/");
            srcFileNames[i] = splitFileUrl[splitFileUrl.length - 1];
        }
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(bucketName + ".zip", "UTF-8"));
        //多个文件压缩成压缩包返回
        ZipUtil.zip(response.getOutputStream(), srcFileNames, srcFiles);
    }

    /**
     * LocalDateTime转换为Date
     *
     * @param localDateTime
     */
    public static Date localDateTime2Date(LocalDateTime localDateTime) {
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = localDateTime.atZone(zoneId);
        Date date = Date.from(zdt.toInstant());
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        // 由于获取的时间存在时间差，我这里手动加上16小时
        cal.add(Calendar.HOUR_OF_DAY, 16);
        date = cal.getTime();
        return date;
    }

}
